"""
@File    :   image.py
@Time    :   2024/03/18 08:35:37
@Author  :   Qrj
@Email   :   923207736@qq.com
@description   :   图像验证码相关工具类
"""

import base64
import random
import string

from asyncer import asyncify
from captcha.image import ImageCaptcha
from redis.asyncio import Redis

from src.utils.uuid import UuidUtil

# 图像验证码生成对象实例
captcha_image = ImageCaptcha()
# 默认可选的字符范围
default_char_list = string.ascii_uppercase + string.digits


class ImageCaptchaUtil:
    """验证码工具类"""

    image_captcha_hash_table_name = "image_captcha_hash_table"

    @staticmethod
    def generate_image_captcha_base64(
        char_list: str = default_char_list, code_len: int = 4
    ) -> tuple[str, str]:
        """生成图像验证码

        Args:
            char_list (str, optional): 可选的字符范围
            code_len (int, optional): 验证码长度

        Returns:
            tuple[str, str]: 验证码结果、base64图像结果
        """
        # 选择字符
        char_res = "".join(random.sample(char_list, code_len))
        # 生成验证码bytes
        result: bytes = captcha_image.generate(char_res).read()
        # 将验证码转为
        base64_str = "data:image/png;base64," + base64.b64encode(result).decode()
        return (char_res, base64_str)

    @staticmethod
    async def async_generate_image_captcha_base64(
        char_list: str = default_char_list, code_len: int = 4
    ) -> tuple[str, str]:
        """生成图像验证码

        Args:
            char_list (str, optional): 可选的字符范围
            code_len (int, optional): 验证码长度

        Returns:
            tuple[str, str]: 验证码结果、base64图像结果
        """
        return await asyncify(ImageCaptchaUtil.generate_image_captcha_base64)(
            char_list=char_list, code_len=code_len
        )

    @staticmethod
    async def create_image_captcha(
        data_redis_pool: Redis,
        code_id: str | None = None,
        code_answer: str | None = None,
        images_base64_str: str | None = None,
        batch_mapping: dict[str, str] | None = None,
    ) -> tuple[str, str, str]:
        """向缓存中写入图像验证码信息

        Args:
            data_redis_pool (Redis): redis连接池
            code_id (str | None, optional): 验证码唯一ID
            code_answer (str | None, optional): 验证码答案
            images_base64_str (str | None, optional): 验证码图像信息
            batch_mapping (dict[str, str] | None, optional): 需要批量插入的图像验证码数据

        Returns:
            tuple[str, str, str]: 验证码ID、验证码答案、验证码图像信息

        """
        # 构建验证码唯一ID
        if code_id is None:
            code_id: str = UuidUtil.generate_uuid1()
        # 构建验证码答案与图像信息
        if code_answer is None or images_base64_str is None:
            (
                code_answer,
                images_base64_str,
            ) = await ImageCaptchaUtil.async_generate_image_captcha_base64()
        # 将图像验证码数据写入redis的hash表
        mapping = {code_id: code_answer + ":" + images_base64_str}
        # 如果有需要批量插入的图像验证码数据, 则一起插入
        if batch_mapping:
            mapping.update(batch_mapping)
        await data_redis_pool.hset(
            ImageCaptchaUtil.image_captcha_hash_table_name, mapping=mapping
        )
        return code_id, code_answer, images_base64_str

    @staticmethod
    async def random_get_image_captcha(
        data_redis_pool: Redis,
    ) -> tuple[str, str]:
        """随机获取图像验证码ID与图像信息

        Args:
            data_redis_pool (Redis): redis连接池

        Returns:
            tuple[str, str]: 获取图像验证码ID与图像信息
        """
        result = await data_redis_pool.hrandfield(
            ImageCaptchaUtil.image_captcha_hash_table_name, count=1, withvalues=True
        )
        if result:
            # 有返回信息则拆解出图像验证码ID与图像信息
            code_id, code_info = result
            code_image_base64_str = code_info.split(":", 1)[1]
            return code_id, code_image_base64_str
        else:
            # 没有返回信息则创建、插入并返回
            code_id, _, images_base64_str = await ImageCaptchaUtil.create_image_captcha(
                data_redis_pool
            )
            return code_id, images_base64_str

    @staticmethod
    async def validate_image_captcha(
        data_redis_pool: Redis, code_id: str, code_answer: str
    ) -> bool | None:
        """单纯验证图像验证码

        Args:
            data_redis_pool: redis连接池
            code_id (str): 图像验证码ID
            code_answer (str): 图像验证码答案

        Returns:
            bool | None: 是否验证成功, 未找到验证码信息则返回None
        """
        code_info = await data_redis_pool.hget(
            ImageCaptchaUtil.image_captcha_hash_table_name, code_id
        )
        if code_info is None:
            # 未找到验证码信息则返回None
            return None
        # 拆解出图像验证码ID与图像信息
        cache_code_answer = code_info.split(":", 1)[0]
        # 验证并返回结果(统一转为大写)
        code_answer = code_answer.upper()
        result = code_answer == cache_code_answer
        return result

    @staticmethod
    async def validate_and_del_image_captcha(
        data_redis_pool: Redis, code_id: str, code_answer: str
    ) -> bool | None:
        """验证并删除图像验证码

        Args:
            data_redis_pool: redis连接池
            code_id (str): 图像验证码ID
            code_answer (str): 图像验证码答案

        Returns:
            bool | None: 是否验证成功, 未找到验证码信息则返回None
        """
        # 验证图像验证码
        result = await ImageCaptchaUtil.validate_image_captcha(
            data_redis_pool, code_id, code_answer
        )
        # 找到验证码信息且验证结果正确时则将其从缓存中删除
        if result:
            await data_redis_pool.hdel(
                ImageCaptchaUtil.image_captcha_hash_table_name, code_id
            )
        return result
